package org.fjsei.yewu.bpm;

import com.alibaba.fastjson2.JSON;
import com.querydsl.core.types.Expression;
import io.camunda.zeebe.client.api.response.ActivatedJob;
import io.camunda.zeebe.client.api.worker.JobClient;
import io.camunda.zeebe.spring.client.annotation.JobWorker;
import io.camunda.zeebe.spring.client.annotation.Variable;
import io.camunda.zeebe.spring.client.exception.ZeebeBpmnError;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import md.cm.unit.Unit;
import md.specialEqp.*;
import md.specialEqp.type.PipingUnit;
import md.system.User;
import md.system.UserRepository;
import org.fjsei.yewu.pojo.jsn.EqpDat;
import org.fjsei.yewu.pojo.jsn.IdCrDate;
//import org.fjsei.yewu.pojo.sei.TaskDetailAssDat;
import org.fjsei.yewu.resolver.Comngrql;
//import org.fjsei.yewu.service.core.TaskService;
import org.fjsei.yewu.util.Tool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import jakarta.persistence.Enumerated;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

import static graphql.Assert.*;
import static jakarta.persistence.EnumType.STRING;

/**台账同步流程 BPMN: ledgerSync 的工作者代码。
* */
@Slf4j
@Component
public class LedgerSyncWorker extends Comngrql {
    @Autowired
    private UniversalService universalService;
    private final UserRepository userRepository;
    private final Requests requests;
    private final Equipments equipments;
//    private final TaskService taskService;
   //, TaskService taskService
    public LedgerSyncWorker(UserRepository userRepository, Requests requests, Equipments equipments) {
        this.userRepository = userRepository;
        this.requests = requests;
        this.equipments = equipments;
//        this.taskService = taskService;
    }

//    //必须是报告流转的 FlowStat?
//    private ApprovalStm setApprovalStmStatus(AskForApprProcessVariables variables, RequestSta_Enum status){
//        Request entFlow = entityOf(variables.getEnt(), Request.class);   //或者其他的申请单实体 都 instance of FlowStat
//        if(null==entFlow)   throw new ZeebeBpmnError("notFind", "没找到流转实体");
//        entFlow.setStatus(status);        //当前位于进入审核环节
//        requests.save(entFlow);
//        return null;
//    }
//    private ApprovalStm setApprovalStmStatus(String entId,RequestSta_Enum status){
//        Request entFlow = entityOf(entId, Request.class);   //或者其他的申请单实体 都 instance of FlowStat
//        if(null==entFlow)   throw new ZeebeBpmnError("notFind", "没找到流转实体");
//        entFlow.setStatus(status);        //当前位于进入审核环节
//        requests.save(entFlow);
//        return null;
//    }

    /**局部使用的结构类：设备状态变更
     * */
    @Getter
    @Setter
    @NoArgsConstructor
    public class EqpOpenCloseDat {
        private List<EqpDat>  devs;       //可json提取恢复的不是全面的Entity数据？？
        private String  理由;
        //reg:注册或注销流程 在监察平台做的受理，@检验平台#不涉及这个关口的核准申请。
        @Enumerated(STRING)
        private UseState_Enum   ust;        //所有设备统一变更为一个状态的？  //json存储是字符串"ust": "USE",
    }


    /**局部使用的结构类：设备的单位变更
     * */
    @Getter
    @Setter
    @NoArgsConstructor
    public static class DeviceUnitsDat{
        private List<EqpDat>  devs;
//        private String  理由;   在后端没必要提取的省略掉
        private String   unitZd;        //所有设备统一变更为相同一个配置的单位
        //发送给第三方应用程序接口的有可能需要附加字段:String addon?:来关联反馈的标签定位？ json:非结构化类比IdCrDate做法。
        private IdCrDate  servu;       //通常的非结构化json关联对象存储；恢复验证关联约束。
    }

    /**台账同步的 总入口：
     * 可能读拉取同步 或 写推送监察的同步，可能并非来自通用申请单Request子流程启动而来的；
     * 针对目标有：设备台账，单位台账；
     * 按这里为主预定协议接口？还是从主工程来源的:syno:json{}；若是来自父辈流程要设置输出映射。 主工程和辅助工程临时用交叉拷贝代码解决。
     * 尽量的解耦合？： ？还是直接复用旧的Request对象。
     * 相当于是接续主工程代码；主工程没能运行的逻辑；
     * */
    @Transactional
    @JobWorker(type="push_get_ledger", autoComplete=true)
    public Object  push_get_ledger(final JobClient client, final ActivatedJob job,@Variable Short days,
                                   @Variable String ent,@Variable String data) {
        Request entFlow = entityOf(ent, Request.class);   //或者其他的申请单实体 都 instance of FlowStat
        if(null==entFlow)   throw new ZeebeBpmnError("notFind", "没找到流转实体");
        Map<String, Object> variablem=new HashMap<>();
        //这两流程图的变量：准备来扩展可能的流程节点？
        Boolean pushNR = null;     //BPMN 不是等待消息的&& push;
        Boolean syncm = null;       //BPMN 立刻得到推送结果的吗
        boolean allDone = false;     //若需采用异步回调来确保业务目的的达成与否的，就设置allDone=false。但是请确保有独立模块或机制执行回调。
        switch (entFlow.getMod()) {
            case "deviceUnits" ->{
                String res= do_deviceUnits(entFlow);
                allDone=true;       //直接完成了；无需要等待监察的确认了吗？
                pushNR=true;
                syncm=true;
            }
            case "eqpOpenclose" ->{
                String res= do_eqpOpenclose(entFlow);
                allDone=true;       //直接完成了；无需要等待监察的确认了吗？
                pushNR=true;
                syncm=true;
            }
            case "technicalField" ->{
                String res= do_technicalField(entFlow);
                allDone=true;       //直接完成了；无需要等待监察的确认了吗？
                pushNR=true;
                syncm=true;
            }
            default -> assertShouldNeverHappen("台账同步流程不认识该申请单mod:"+entFlow.getMod());
        }
//        log.info("设备状态变更申请单, 审批处置结束：{}", ent);
        if(allDone){        //不需要等待监察平台反馈的
            days=0;
        }
        variablem.put("pushNR", pushNR);
        variablem.put("syncm", syncm);
        //只考虑后端执行业务操作 && 无需等待监察平台的：直接设置END
//        RequestSta_Enum status =(null!=days && days>0)? RequestSta_Enum.FIN : RequestSta_Enum.END;
//        entFlow.setStatus(status);        //当前位于进入审核环节
//        entFlow.setMdays(days);     //@特殊：需要存档超期天数，days天数过后，假如监察反馈机制没有起作用的该如何，自动把FIN改成END;
//        requests.save(entFlow);
        log.info("台账同步的流程完成ent={} audat:{}", ent, data);      //有些需要等候后续监察平台反馈，才算本申请单实体生命周期结束
        return variablem;
    }
    /**能够进入这一节点的，都是写更新从申请单走过来的。
     * */
    @JobWorker(type="request_callback_alldone", autoComplete=true)
    @Transactional
    public void request_callback_alldone(@Variable String ent,@Variable String approved,@Variable String manager,@Variable String data) throws Exception {
        Request entFlow = entityOf(ent, Request.class);   //或者其他的申请单实体 都 instance of FlowStat
        if(null==entFlow)   throw new ZeebeBpmnError("notFind", "没找到流转实体");
        entFlow.setStatus(RequestSta_Enum.END);
        requests.save(entFlow);
        log.info("台账同步 push已结束：ent={}",ent);
    }

    /**设备状态变更; 发送监察平台的情况部分，监察可能要审核的。
     * 【不同的监察】监察机构可能并非一家的平台的，还得分解成很多个可能性：福建省监察平台...等等。操作不同监察平台的对接接口。
     * */
    public String  do_eqpOpenclose(Request entFlow) {
        String authorDat=entFlow.getDat();
        //耦合代码：提取非结构化的申请单业务信息。
        EqpOpenCloseDat dat = JSON.parseObject(authorDat, EqpOpenCloseDat.class);
        List<Eqp> eqps=new ArrayList<Eqp>();        //校验json恢复设备crDate+uuID;避免指向错误设备。
        LocalDate today=LocalDate.now();       //今天的 ：注意服务器时间同步。时间差不能太大，NTP 有毛病的。
        dat.getDevs().forEach(item -> {
            Eqp eqp=entityOf(item.getId(),Eqp.class);       //对接多个第三方的监察平台？默认福建省的；
            assertNotNull(eqp, () -> "没该设备");
            assertNotNull(item.getCrDate(), () -> "crDate空");
            assertTrue(item.getCrDate().equals(eqp.getCrDate()), () -> "crDate不一致");
            assertTrue(item.getCrDate().isBefore(today), () -> "crDate不牢靠");
            eqps.add(eqp);
        });
        AtomicReference<String> subp = new AtomicReference<>("");
        eqps.forEach(eqp -> {
            if(RegState_Enum.TODOREG==eqp.getReg() || RegState_Enum.CANCEL==eqp.getReg()){
//                eqp.setUst(dat.getUst());
//                log.info("设备状态变更申请单,本平台直接变更：cod={}", eqp.getCod());
            }else{
                log.info("设备状态变更 转交给监察平台 oid={}, cert={}", eqp.getOid(),eqp.getCert());
                // todo: //这里假定有 监察对应接口操作 已完成申请单内容的提交。
            }
        });
//        equipments.saveAll(eqps);      //监察变更真的完成了，还要依靠另外有途径的，消息驱动？手动同步：最终才回头更新本平台的设备状态？。
//        requests.save(entFlow);
//        Map<String, Object>  variablem=new HashMap<>();
//        log.info("设备状态变更申请单, 审批处置结束：{}", ent);     //【奇怪】为何这个节点等待5分多钟时间啊，毛病
        return subp.get();       //不需要等待监察平台反馈的
    }
    /**设备的单位变更, 发送监察平台的情况部分;
     * 【实质上】一个worker进行拆分：一半主工程，另一半变身子流程单独地在这里辅助工程地后端服务器上运作地。稍微有点绕：好处对主工程屏蔽掉部分复杂性逻辑，可部分替换整改升级版本。
     * */
    public String  do_deviceUnits(Request entFlow) {
        String authorDat=entFlow.getDat();
        //耦合代码：提取非结构化的申请单业务信息。
        DeviceUnitsDat dat = JSON.parseObject(authorDat, DeviceUnitsDat.class);
        assertNotNull(dat.getServu(), () -> "没选单位");
        //旧版本Unit unit=fromInputUnitGlobalID(dat.getServu().getId());
        Unit unit= entityOf(dat.getServu().getId(), Unit.class);
        assertNotNull(unit, () -> "没该单位");
        assertTrue(Objects.equals(unit.getCrDate(), dat.getServu().getCrDate()), () -> "crDate不一致");

        List<Eqp> eqps=new ArrayList<Eqp>();        //校验json恢复设备crDate+uuID;避免指向错误设备。
        LocalDate today=LocalDate.now();       //今天的 ：注意服务器时间同步。时间差不能太大，NTP 有毛病的。
        dat.getDevs().forEach(item -> {
            Eqp eqp=entityOf(item.getId(),Eqp.class);       //对接多个第三方的监察平台？默认福建省的；
            assertNotNull(eqp, () -> "没该设备");
            assertNotNull(item.getCrDate(), () -> "crDate空");
            assertTrue(item.getCrDate().equals(eqp.getCrDate()), () -> "crDate不一致");
            eqps.add(eqp);
        });
        AtomicReference<String> subp = new AtomicReference<>("");
        eqps.forEach(eqp -> {
            if(RegState_Enum.TODOREG==eqp.getReg() || RegState_Enum.CANCEL==eqp.getReg()){
                switch (dat.unitZd) {
                    case "useu" -> eqp.setUseu(unit);
                    case "owner" -> eqp.setOwner(unit);
                    case "mtu" -> eqp.setMtu(unit);
                    default -> assertTrue(true, () -> "非法unitZd");
                }
                log.info("设备单位变更申请单,本平台直接变更：cod={}", eqp.getCod());
            }else{
                //需要监察平台来主导变更的，发送给对方平台，离线等待监察的反馈。
                //todo:// 监察平台实际变更操作接口，或提交监察去审批执行。 不同监察平台：新旧平台。尽量避免拆解申请单最好我方申请单一一对应的。
                log.info("设备单位变更 交给监察平台 oid={}, cert={}", eqp.getOid(),eqp.getCert());
            }
            //仰仗监察接口驱动本功能了。本地设备台账修改没有长期效果的，必须源头变更。除非这个设备是本平台自己才有的设备。
            //单位指代还必须和监察平台的是同一个公司：关键字组合（no,..name,）
            //为每个设备给监察平台发出具体状态变革申请。可能分解成多个的监察处理单子啊？？
        });
        equipments.saveAll(eqps);
        return subp.get();
    }
    /**局部使用的结构类：关键技术参数变更; 实际上多数的监察才能修改的参数字段并不会使用本途径做变更申请的，更多是首检初始化参数，大修施工告知时刻监察就做主动参数变更的。所以本申请单实际数量不会太多。
     * */
    @Getter
    @Setter
    @NoArgsConstructor
    public class EqpTechnicalFieldDat {
        //是否针对管道单元进行的关键技术参数变更？ 默认=false;
        private Boolean  pipe;   //若是管道单元，那么devs就应当只能唯一一个Eqp:Pipeline，不要混合多个管道装置;
        private List<EqpDat>  devs;       //可json提取恢复的不是全面的Entity数据？？
        //挑选管道单元的情况！ 不是正常设备， 设备->多个管道单元：管道单元底下的几个参数的。

        private String  理由;
        private String  eqpType;
        //reg:注册或注销流程 在监察平台做的受理，@检验平台#不涉及这个关口的核准申请。 对应前端的export const 关键参数typec={;
        //字段名字： Eqp.<>::实体模型表的属性名字。 #注意：这个配置仅仅挑选参数名，并没有配置每个参数的最新取值。【哪些字段】需要推送的：由业务高层层次定夺,不一定在底层来限定死的。
        private List<String>  paramx;       //所有的设备：统一将会变更这些字段的！一起推送给监察平台。【注意】最好是同一个监察平台，多个平台分叉的麻烦？！
    }
    /**设备关键技术参数的变更， 发送监察平台的部分 todo://每个参数字段映射到监察平台
     * 这个和主工程RequestWorker：的同名函数功能目标不同，后者只做检查，没有实际给监察。
     * */
    public String  do_technicalField(Request entFlow) {
        String authorDat=entFlow.getDat();
        EqpTechnicalFieldDat dat = JSON.parseObject(authorDat, EqpTechnicalFieldDat.class);
        LocalDate today=LocalDate.now();       //今天的 ：注意服务器时间同步。时间差不能太大，NTP 有毛病的。
        AtomicReference<String> subp = new AtomicReference<>("");
        if(dat.getPipe()){
            List<PipingUnit> eqps=new ArrayList<PipingUnit>();        //校验json恢复设备crDate+uuID;避免指向错误设备。
            dat.getDevs().forEach(item -> {
                PipingUnit eqp=entityOf(item.getId(),PipingUnit.class);       //对接多个第三方的监察平台？默认福建省的；
                assertNotNull(eqp, () -> "没该设备");
                assertNotNull(item.getCrDate(), () -> "crDate空");
                assertTrue(item.getCrDate().equals(eqp.getCrDate()), () -> "crDate不一致");
                assertTrue(item.getCrDate().isBefore(today), () -> "crDate不牢靠");
                eqps.add(eqp);
            });
            eqps.forEach(eqp -> {
                if(RegState_Enum.TODOREG==eqp.getReg() || RegState_Enum.CANCEL==eqp.getReg()){
                    log.info("管道单元关键技术参数的变更,本平台直接变更：code={}", eqp.getCode());
                }else{
                    log.info("管道单元关键技术参数的变更 转交给监察平台 rno={}", eqp.getRno());
                    //【一致性】这直接提取台账参数取值是没问题；可惜不一定是审核人看到的取自啊！还是让前端把参数值配置到申请单json固定下来，避免申请单提交后参数还可能被修改的情况！
                    //Object someval= Tool.reflectGetValueByKey(eqp, "mom");
                    //if(someval instanceof Boolean) ;else if(someval instanceof Number) ;else if(someval instanceof String) ;//除了是svp.xxx的其它是Eqp.getxxx();

//                log.info("设备关键技术参数的变更 mom oid={}, someval={}", eqp.getOid(), someval);

                    dat.getParamx().forEach(ofield -> {
                        //不同设备种类eqpType：每一个具体参数：映射到各个监察平台对方的参数名字，参数值的类型转换？json前端都用string"true/3.563/?";
                    });
                }
            });
        }else {
            List<Eqp> eqps=new ArrayList<Eqp>();        //校验json恢复设备crDate+uuID;避免指向错误设备。
            dat.getDevs().forEach(item -> {
                Eqp eqp=entityOf(item.getId(),Eqp.class);       //对接多个第三方的监察平台？默认福建省的；
                assertNotNull(eqp, () -> "没该设备");
                assertNotNull(item.getCrDate(), () -> "crDate空");
                assertTrue(item.getCrDate().equals(eqp.getCrDate()), () -> "crDate不一致");
                assertTrue(item.getCrDate().isBefore(today), () -> "crDate不牢靠");
                eqps.add(eqp);
            });
            eqps.forEach(eqp -> {
                if(RegState_Enum.TODOREG==eqp.getReg() || RegState_Enum.CANCEL==eqp.getReg()){
                    log.info("设备关键技术参数的变更,本平台直接变更：cod={}", eqp.getCod());
                }else{
                    log.info("设备关键技术参数的变更 转交给监察平台 oid={}, cert={}", eqp.getOid(),eqp.getCert());
                    //【一致性】这直接提取台账参数取值是没问题；可惜不一定是审核人看到的取自啊！还是让前端把参数值配置到申请单json固定下来，避免申请单提交后参数还可能被修改的情况！
                    //Object someval= Tool.reflectGetValueByKey(eqp, "mom");
                    //if(someval instanceof Boolean) ;else if(someval instanceof Number) ;else if(someval instanceof String) ;//除了是svp.xxx的其它是Eqp.getxxx();

//                log.info("设备关键技术参数的变更 mom oid={}, someval={}", eqp.getOid(), someval);

                    dat.getParamx().forEach(ofield -> {
                        //不同设备种类eqpType：每一个具体参数：映射到各个监察平台对方的参数名字，参数值的类型转换？json前端都用string"true/3.563/?";
                    });
                }
            });
        }
//        equipments.saveAll(eqps);      //监察变更真的完成了，还要依靠另外有途径的，消息驱动？手动同步：最终才回头更新本平台的设备状态？。
//        requests.save(entFlow);
//        Map<String, Object>  variablem=new HashMap<>();
//        log.info("设备状态变更申请单, 审批处置结束：{}", ent);     //【奇怪】为何这个节点等待5分多钟时间啊，毛病
        return subp.get();       //不需要等待监察平台反馈的
    }
}


//问题：Job worker 工作者代码必须是幂等的 ？有些确实不好保证 幂等性，执行一次就得多出一个的情况？状态控制。变更状态，单位变更，某个属性技术参数的变更，重复变更申请的能够接受吗；读取；
//worker【幂等性】如何确保。 #可以由对方的监察应答这一块来确保，监察审核的若已经存在同一个单子的就直接返回成功ack应答。
//直接用zeebe-client.Api太不友好, 没法直接的对UserTask读取当前流程实例  https://github.com/camunda-community-hub/spring-zeebe